//
//  MAPNSObject+BlockObservation.m
//

#import "MAPNSObject+BlockObservation.h"
#import "MAPNSObject+AssociatedObjects.h"
#import "MAPNSDictionary+BlocksKit.h"

void useCatagory5(){
    NSLog(@"do nothing, just for make catagory linked");
}

@interface BKObserver : NSObject

@property (nonatomic, assign) id observee;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) MAPBlocksObservationBlock task;

+ (BKObserver *)observerForObject:(id)observee keyPath:(NSString *)keyPath task:(MAPBlocksObservationBlock)task;

@end

static char kObserverBlocksKey;
static char kBlockObservationContext;

@implementation BKObserver

@synthesize observee, keyPath, task;

+ (BKObserver *)observerForObject:(id)observee keyPath:(NSString *)newKeyPath task:(MAPBlocksObservationBlock)newTask {
	BKObserver *instance = [BKObserver new];
	instance.observee = observee;
	instance.keyPath = newKeyPath;
	instance.task = newTask;
	return [instance autorelease];
}

- (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if (context == &kBlockObservationContext)
		self.task(object, change);
}

- (void)dealloc {
	self.task = nil;
	self.keyPath = nil;
	[super dealloc];
}

@end


static dispatch_queue_t BKObserverMutationQueue() {
	static dispatch_queue_t queue = nil;
	static dispatch_once_t token = 0;
	dispatch_once(&token, ^{
		queue = dispatch_queue_create("org.blockskit.observers.queue", 0);
	});
	return queue;
}

@implementation NSObject (MAPBlockObservation)

- (NSString *)addObserverForKeyPath:(NSString *)keyPath task:(MAPBlocksObservationBlock)task {
	NSString *token = [[NSProcessInfo processInfo] globallyUniqueString];
	[self addObserverForKeyPath:keyPath identifier:token task:task];
	return token;
}

- (void)addObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier task:(MAPBlocksObservationBlock)task {
	NSParameterAssert(keyPath);
	NSParameterAssert(identifier);
	NSParameterAssert(task);
	
	__block BKObserver *newObserver = nil;
	
	dispatch_sync(BKObserverMutationQueue(), ^{
		newObserver = [BKObserver observerForObject:self keyPath:keyPath task:task];
		
		NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
		if (!dict) {
			dict = [NSMutableDictionary dictionary];
			[self associateValue:dict withKey:&kObserverBlocksKey];
		}
		
		[dict setObject:newObserver forKey:[NSString stringWithFormat:@"%@_%@", keyPath, identifier]];
	});
	
	[self addObserver:newObserver forKeyPath:keyPath options:0 context:&kBlockObservationContext];
}

- (NSString *)addObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options task:(MAPBlocksObservationBlock)task {
	NSString *token = [[NSProcessInfo processInfo] globallyUniqueString];
	[self addObserverForKeyPath:keyPath identifier:token options:options task:task];
	return token;
}

- (void)addObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options task:(MAPBlocksObservationBlock)task {
	NSParameterAssert(keyPath);
	NSParameterAssert(identifier);
	NSParameterAssert(task);
	
	__block BKObserver *newObserver = nil;
	
	dispatch_sync(BKObserverMutationQueue(), ^{
		newObserver = [BKObserver observerForObject:self keyPath:keyPath task:task];
		
		NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
		if (!dict) {
			dict = [NSMutableDictionary dictionary];
			[self associateValue:dict withKey:&kObserverBlocksKey];
		}
		
		[dict setObject:newObserver forKey:[NSString stringWithFormat:@"%@_%@", keyPath, identifier]];
	});
	
	[self addObserver:newObserver forKeyPath:keyPath options:options context:&kBlockObservationContext];
}

- (void)removeObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)identifier {
	NSParameterAssert(keyPath);
	NSParameterAssert(identifier);
	
	dispatch_sync(BKObserverMutationQueue(), ^{
		NSString *token = [NSString stringWithFormat:@"%@_%@", keyPath, identifier];
		NSMutableDictionary *dict = [self associatedValueForKey:&kObserverBlocksKey];
		BKObserver *trampoline = [dict objectForKey:token];
		
		if (!trampoline || ![trampoline.keyPath isEqualToString:keyPath])
			return;
		
		[self removeObserver:trampoline forKeyPath:keyPath];
		
		[dict removeObjectForKey:token];
		
		if (!dict.count)
			[self associateValue:nil withKey:&kObserverBlocksKey];
	});
}

- (void)removeAllBlockObservers {
	dispatch_sync(BKObserverMutationQueue(), ^{
		NSMutableDictionary *observationDictionary = [self associatedValueForKey:&kObserverBlocksKey];
		[observationDictionary each:^(id key, id trampoline) {
			[self removeObserver:trampoline forKeyPath:[trampoline keyPath]];
		}];
		[self associateValue:nil withKey:&kObserverBlocksKey];
	});
}

@end
